using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

namespace Predictor
{
    /// <summary>
    /// Implementation of a prefix tree (Trie) thath can be trained 
    /// Author: Panos
    /// </summary>

    public class Trie
    {
        #region PrivateDataMembers

        private int size;
        private int popularity;
		private Dictionary<char, Trie> subTries;

        #endregion

		#region PublicMethods

		public Trie()
        {
            popularity = 0;
            size = 1;
			
			subTries = new Dictionary<char, Trie>();
			subTries.Add('a', null);
			subTries.Add('b', null);
			subTries.Add('c', null);
			subTries.Add('d', null);
			subTries.Add('e', null);
			subTries.Add('f', null);
			subTries.Add('g', null);
			subTries.Add('h', null);
			subTries.Add('i', null);
			subTries.Add('j', null);
			subTries.Add('k', null);
			subTries.Add('l', null);
			subTries.Add('m', null);
			subTries.Add('n', null);
			subTries.Add('o', null);
			subTries.Add('p', null);
			subTries.Add('q', null);
			subTries.Add('r', null);
			subTries.Add('s', null);
			subTries.Add('t', null);
			subTries.Add('u', null);
			subTries.Add('v', null);
			subTries.Add('w', null);
			subTries.Add('x', null);
			subTries.Add('y', null);
			subTries.Add('z', null);
        }

		/// <summary>
		/// Adds an new word in the Trie
		/// </summary>
		/// <param name="word">
		/// A <see cref="System.String"/>
		/// The word that will be inserted
		/// </param>



		public void Add(string word)
        {
            /* The word must be valid, because it's loaded from the Clean Wordnet file */

			Debug.Assert(ValidWord(word));
			
            /*Ignore capitals */

			word = word.ToLower();
			
			InnerAdd(word);
		}

		/// <summary>
        /// Loads words from the file specified. The file must have the following format: word1\nword2\n[...]
        /// Author: Panos
        /// </summary>
        /// <param name="filename">The file url to open</param>

		
        public void LoadWordsFromFile(string filename)
        {
            StreamReader reader = File.OpenText(filename);

            string word;
            
			while ((word = reader.ReadLine()) != null)
            {
                this.Add(word);
            }
			
			reader.Close();
        }
		

		/// <summary>
        /// Clears the Trie from any added words and information
        /// Author: Panos
        /// </summary>

        public void Clear()
        {
			subTries.Clear();
		}


		/// <summary>
		/// Returns the size of the sub Trie with root the given parameter letter
		/// Author: Panos
		/// </summary>
		/// <param name="letter">
		/// A <see cref="System.Char"/> indicating the sub trie 
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/> indicating the size of the specified sub Trie
		/// </returns>

		
		public int GetSubTrieSize(char letter)
		{
			/* Ignore case sensitivity */
			
			letter = Char.ToLower(letter);
			
			return subTries[letter] != null ? subTries[letter].Size() : 0;
		}

		/// <summary>
        /// Returns the number of the words that the Trie contains
        /// Author: Panos
        /// </summary>
        /// <returns></returns>

		
        public int Size()
        {
			return size;
        }


		/// <summary>
        /// Informs the Trie that the given word was typed in the past and how many times.
        /// The word given must be added in the past in the Trie
        /// Author: Panos
        /// </summary>
        /// <param name="word">The word that was typed in the past</param>
        /// <param name="times">How many times the given word parameter was typed</param>

        public void WasTyped(string word, int times)
        {
            if (String.IsNullOrEmpty(word))
            {
                popularity += times;
                return;
            }

            popularity += times;
			
			/* Ignore case sensitivity */
			
			word = word.ToLower();
			
            char firstChar = word[0];
            string postfix = word.Substring(1);

            /* The word typed in the past, must exist in the Trie */

            Debug.Assert(subTries[firstChar] != null);

            subTries[firstChar].WasTyped(postfix, times);
        }


		/// <summary>
		/// Returns the subtrie with root the character given as parameter
		/// Author: Panos
		/// </summary>
		/// <param name="characterTyped">
		/// A <see cref="System.Char"/> indicating the root of the subtrie that we want
		/// </param>
		/// <returns>
		/// A <see cref="Trie"/> subtrie with root the given character
		/// </returns>

		public Trie GetSubTrie(char characterTyped)
		{
			/* Ignore case sensitivity */
			
			characterTyped = Char.ToLower(characterTyped);

            return subTries[characterTyped] != null ? subTries[characterTyped] : null;			
		}		


		/// <summary>
		/// Returns the popularity of the nextLetter
		/// Author: Panos
		/// </summary>
		/// <param name="nextLetter">
		/// A <see cref="System.Char"/>
		/// </param>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
				
		public int GetPopularity(char nextLetter)
		{
			/* Ignore case sensitivity */
			
			nextLetter = Char.ToLower(nextLetter);
			
			return subTries[nextLetter] != null ? subTries[nextLetter].GetPopularity() : 0;
		}
		
        #endregion

        #region PrivateMethods

        /// <summary>
		/// The actual Add method of the Trie. The public void Add(string word) is a wrapper that
		/// checks for validity and converts the word to lower case and gives the word as parameter to this method
        /// Author: Panos
		/// </summary>
		/// <param name="word">
		/// A <see cref="System.String"/>
		/// </param>
		
		private void InnerAdd(string word)
		{			
			if (String.IsNullOrEmpty(word))
			{
				return;				
			}

			char firstChar = word[0];
			string postfix = word.Substring(1);
			
			if(subTries[firstChar] == null)
			{
				subTries[firstChar] = new Trie();				
			}
			
			subTries[firstChar].InnerAdd(postfix);			
			size++;
		}


        /// <summary>
        /// Searches the Trie for the given word (for debug purpose)
        /// Author: Panos
        /// </summary>
        /// <param name="word">
        /// A <see cref="System.String"/>
        /// </param>
        /// <returns>
        /// A <see cref="System.Boolean"/>
        /// if the word was found in the Trie
        /// </returns>

        public bool Search(string word)
        {
			if (String.IsNullOrEmpty(word))
			{
				return true;
			}			

			char firstChar = word[0];
			
			if(subTries[firstChar] == null)
			{
				return false;
			}
			else
			{
				string postfix = word.Substring(1);
				return subTries[firstChar].Search(postfix);			
			}			
		}
		
		
		/// <summary>
		/// Returns the popularity of this node
		/// Author: Panos
		/// </summary>
		/// <returns>
		/// A <see cref="System.Int32"/>
		/// </returns>
		
		private int GetPopularity()
		{
			return popularity;
		}		

		
        /// <summary>
        /// If the word parameter contains numbers, ', ., _, -, (, ) or /
        /// returns false, otherwise returns true.
        /// Author: Panos
        /// </summary>
        /// <param name="word">The word to be examined if is valid word for the Trie</param>
        /// <returns></returns>

        private bool ValidWord(string word)
        {
            if (word.Contains("0"))
                return false;
            if (word.Contains("1"))
                return false;
            if (word.Contains("2"))
                return false;
            if (word.Contains("3"))
                return false;
            if (word.Contains("4"))
                return false;
            if (word.Contains("5"))
                return false;
            if (word.Contains("6"))
                return false;
            if (word.Contains("7"))
                return false;
            if (word.Contains("8"))
                return false;
            if (word.Contains("9"))
                return false;
            if (word.Contains("'"))
                return false;
            if (word.Contains("."))
                return false;
            if (word.Contains("-"))
                return false;
            if (word.Contains("_"))
                return false;
            if (word.Contains("/"))
                return false;
            if (word.Contains("("))
                return false;
            if (word.Contains(")"))
                return false;
            return true;
        }
		
		#endregion
    }
}